Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emmet - Wrap with abbreviation with live preview #45092

Merged
merged 17 commits into from
Mar 7, 2018

Conversation

gushuro
Copy link
Contributor

@gushuro gushuro commented Mar 5, 2018

Adding ability to have a preview of the resulting text when calling the Wrap with Abbreviation command, as the user types in the input box.
Feature request here #23169

Gustavo Martin Hurovich added 7 commits March 2, 2018 18:02
Currently it's only working for single cursor.
- Removed flickering when typing abbreviation
- Removed tabstops
- Fixed bug when wrapping multiline text
- Added checks for not reverting previews more times than needed, that was causing extra text to be deleted.
- Fixed issue when wrapping nodes with multiple level of indentation.
- Removed all the undo commands. Now all the logic of going back to the original state is handled by revertPreview.
Refactoring some of the code, now a single object contains the current and original ranges, as well as the original content to wrap
@gushuro gushuro requested a review from ramya-rao-a March 5, 2018 22:37
Copy link
Contributor

@ramya-rao-a ramya-rao-a left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial comments

@@ -24,6 +24,12 @@ interface ExpandAbbreviationInput {
filter?: string;
}

interface RangesAndContent {
currentRange: vscode.Range;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The currentRange here will only have the preview ranges correct? If so, let's rename it to previewRange to make it more readable.
Also rename content to originalContent to match with the originalRange and RangesAndContent to PreviewRangesWithContent

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

}

// On inputBox closing
return abbreviationPromise.then(inputAbbreviation => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests are currently failing as the promise returned by makeChanges is not being returned by wrapWithAbbreviation

Since makeChanges does a revertPreview when given abbreivation is null/undefined/invalid, can we not simplify this to return abbreviationPromise.then(inputAbbreviation => makeChanges(inputAbbreviation, previewMade, true)); ?

if (!inputAbbreviation || !inputAbbreviation.trim() || !helper.isAbbreviationValid(syntax, inputAbbreviation)) { return false; }
function makeChanges(inputAbbreviation: string | undefined, previewMade?: boolean, definitive?: boolean): Thenable<any> {
if (!inputAbbreviation || !inputAbbreviation.trim() || !helper.isAbbreviationValid(syntax, inputAbbreviation)) {
let returnPromise: Thenable<any> = Promise.resolve();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can condense these 5 lines to return previewMade ? revertPreview(editor, rangesToReplace) : Promise.resolve();

returnPromise = revertPreview(editor, rangesToReplace).then(() => { return false; });
}
return returnPromise;
}

let extractedResults = helper.extractAbbreviationFromText(inputAbbreviation);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be an exisitng bug... but shouldnt we check for the validity of the abbreviation after it is extracted instead of before?
Also, if extractedResults is empty then shouldnt we revert the previous preview?

I would suggest to move the extraction to be the first thing that we do in makeChanges(). Then,

if (!extractedResults || !helper.isAbbreviationValid(syntax, extractedResults.abbreviation)) {
return previewMade ? revertPreview(editor, rangesToReplace) : Promise.resolve();
}

}
rangesToReplace.forEach(rangesAndContent => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The currentRange of each rangesToReplace array gets updated inside revertPreview. Since the originalRange doesnt change, it is safe to loop through and use it here before revertPromise is completed. But for the sake of readability, it is better to do this after `revertPromise is completed.

rangesToReplace.forEach(rangesAndContent => {
let rangeToReplace = rangesAndContent.originalRange;
let textToWrap = rangeToReplace.isSingleLine ? ['$TM_SELECTED_TEXT'] : ['\n\t$TM_SELECTED_TEXT\n'];
expandAbbrList.push({ syntax: syntax || '', abbreviation, rangeToReplace, textToWrap, filter });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would syntax be null/undefined here?

let textToWrap = rangeToReplace.isSingleLine ? ['$TM_SELECTED_TEXT'] : ['\n\t$TM_SELECTED_TEXT\n'];
expandAbbrList.push({ syntax, abbreviation, rangeToReplace, textToWrap, filter });
return Promise.resolve().then(() => {
return applyPreview(editor, expandAbbrList, rangesToReplace).then(ranges => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why return the ranges here from applyPreview and the update rangesToReplace?
rangesToReplace is accessible from inside applyPreview and can be updated from there as well.
Just like you did in revertPreview

@@ -90,45 +89,34 @@ export function wrapWithAbbreviation(args: any) {

function makeChanges(inputAbbreviation: string | undefined, previewMade?: boolean, definitive?: boolean): Thenable<any> {
if (!inputAbbreviation || !inputAbbreviation.trim() || !helper.isAbbreviationValid(syntax, inputAbbreviation)) {
return previewMade ? revertPreview(editor, rangesToReplace).then(() => { return false; }) : Promise.resolve();
return previewMade ? revertPreview(editor, rangesToReplace) : Promise.resolve();
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning false allows us to not revert the preview several times. That would not a problem per se, since revertPreview only replaces the current range with the original content, but why make extra unnecessary edits?

function inputChanged(value: string): string {
if (value !== currentValue) {
currentValue = value;
makeChanges(value, previewMade, false).then((out) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if inputChanged gets called before the previous makeChanges completes?

let indentPrefix = '';
let preceedingText = editor.document.getText(new vscode.Range(new vscode.Position(thisRange.start.line, 0), thisRange.start));
// If there is only whitespace before the text to wrap, take that as prefix. If not, take as much whitespace as there is before text appears.
if (!preceedingText.match(/[^\s]/)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of this if/else, why not follow the same logic as preceedingWhiteSpace in https://github.com/Microsoft/vscode/blob/master/extensions/emmet/src/abbreviationActions.ts#L66-L68?

}

let newText = expandedText || '';
newText = newText.replace(/\n/g, '\n' + indentPrefix).replace(/\$\{[\d]*\}/g, '|');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not replace placeholders. Try wrapping with !

indentPrefix = (preceedingText.match(/(^[\s]*)/) || ['', ''])[1];
}

let newText = expandedText || '';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would we continue if expandedText is null/undefined/empty?

@ramya-rao-a ramya-rao-a merged commit 2e746d8 into microsoft:master Mar 7, 2018
@github-actions github-actions bot locked and limited conversation to collaborators Mar 27, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants